using Pegasus.Diagnostics;
using System;
using System.Collections;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Messaging;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;

namespace Pegasus.Monitoring
{
	/// <summary>
	/// Summary description for AppMonitor.
	/// </summary>
	[ System.Obsolete( "Use the Monitor.Application.dll", true ) ]
	public sealed class AppMonitor
	{
		// Static Values
		private static Guid s_softwareId = Guid.Empty;
		private static AppMonitorUnhandledExceptions s_exceptions = null;

		private static bool s_sendMessages = true;
		private static MessageQueue s_queue = null;
		private static StatusManager s_statusManager = null;
		
		/// <summary>Trace switch object for this class.</summary>
		private static TraceSwitchEx traceSwitch = TraceSwitchEx.AllocateTraceSwitch(
			"Pegasus.Monitoring.AppMonitor", "Application Monitoring Class" );

		/// <summary>
		/// Static Constructor
		/// </summary>
		static AppMonitor()
		{
			s_exceptions = new AppMonitorUnhandledExceptions();
			OpenMessageQueue();
		}

		/// <summary>
		/// Private constructor to prevent creation of the type.
		/// </summary>
		private AppMonitor()
		{
		}

		/// <summary>
		/// Application id assigned by the monitoring system.
		/// </summary>
		public static Guid SoftwareId
		{
			get
			{
				return s_softwareId;
			}

			set
			{
				s_softwareId = value;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="applicationStatus"></param>
		public static void RegisterApplicationStatus( IApplicationStatus applicationStatus )
		{
			// Check params
			ParamCode.AssertNotNull( applicationStatus, "applicationStatus" );

			if( s_statusManager == null )
			{
				s_statusManager = new StatusManager( applicationStatus );
			}
			else
			{
				throw new ArgumentException( "An interface has already been registered for this AppDomain.", "applicationStatus" );
			}
		}

		/// <summary>
		/// Unregister the current application status interfaces.
		/// </summary>
		public static void UnregisterApplicationStatus()
		{
			if( s_statusManager != null )
			{
				s_statusManager = null;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="metricId"></param>
		/// <param name="value"></param>
		public static void Metric( Guid metricId, int value )
		{
			// Check params
			ParamCode.AssertNotEmpty( metricId, "metricId" );

			SendMetric( metricId, Convert.ToDouble( value ) );
		}
		
		/// <summary>
		/// 
		/// </summary>
		/// <param name="metricId"></param>
		/// <param name="value"></param>
		public static void Metric( Guid metricId, long value )
		{
			// Check params
			ParamCode.AssertNotEmpty( metricId, "metricId" );

			SendMetric( metricId, Convert.ToDouble( value ) );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="metricId"></param>
		/// <param name="value"></param>
		public static void Metric( Guid metricId, double value )
		{
			// Check params
			ParamCode.AssertNotEmpty( metricId, "metricId" );

			SendMetric( metricId, value );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="metricId"></param>
		/// <param name="dateUTC"></param>
		/// <param name="deviceId"></param>
		/// <param name="agentId"></param>
		/// <param name="seconds"></param>
		/// <param name="httpStatusCode"></param>
		public static void MetricWebPages( Guid metricId, DateTime dateUTC, Guid deviceId, Guid agentId, double seconds, int httpStatusCode )
		{
			// Check params
			DebugCode.AssertNotEmpty( metricId );
			// date can be any value date
			// deviceId can be empty
			DebugCode.AssertNotEmpty( agentId );
			DebugCode.AssertRange( seconds, 0.0, double.MaxValue );
			DebugCode.AssertRange( httpStatusCode, -1, int.MaxValue );

			if( CheckReadyToSend() )
			{
				Hashtable table = new Hashtable();
				table[ TableNames.SoftwareId ] = s_softwareId;
				table[ TableNames.MetricId ] = metricId;
				table[ TableNames.DateUTC ] = dateUTC;
				table[ TableNames.DeviceId ] = deviceId;
				table[ TableNames.AgentDeviceId ] = agentId;
				table[ TableNames.Value ] = seconds;
				table[ TableNames.HttpStatusCode ] = httpStatusCode;

				SendMessage( table, MessageLabelNames.MetricWebPages );
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="e"></param>
		public static void Exception( Exception e )
		{
			// Check params
			ParamCode.AssertNotNull( e, "e" );

			SendException( e, null, null );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="e"></param>
		/// <param name="message"></param>
		/// <param name="args"></param>
		public static void Exception( Exception e, string message, params object[] args )
		{
			// Check params
			ParamCode.AssertNotNull( e, "e" );
			// message can be null or empty
			// args can be null

			SendException( e, message, args );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="alertId"></param>
		public static void Alert( Guid alertId )
		{
			// TODO implement
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="alertId"></param>
		/// <param name="message"></param>
		/// <param name="args"></param>
		public static void Alert( Guid alertId, string message, params object[] args )
		{
			// TODO implement
		}
/*
		/// <summary>
		/// 
		/// </summary>
		/// <param name="e"></param>
		/// <param name="message"></param>
		public static void Message( string message, params object[] args )
		{
			// TODO send context message
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="methodName"></param>
		/// <param name="parameters"></param>
		public static void MethodInformation( string methodName, params object[] parameters )
		{
			// TODO send context message
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="methodName"></param>
		/// <returns></returns>
		public static MethodTimer MethodTimer( string methodName )
		{
			// TODO Call Context id
			return new MethodTimer( methodName, Guid.Empty, true );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="methodName"></param>]
		/// <param name="parameters">The parameters passed to the method being timed</param>
		/// <returns></returns>
		public static MethodTimer MethodTimer( string methodName, params object[] parameters )
		{
			// TODO Call Context id
			return new MethodTimer( methodName, Guid.Empty, true );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="callContextId"></param>
		public static IDisposable PushThreadCallContextId()
		{
			// TODO New call context id
			return (IDisposable) new AppMonitorCallContext();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="callContextId"></param>
		public static IDisposable PushThreadCallContextId( Guid callContextId )
		{
			return (IDisposable) new AppMonitorCallContext();
		}

		/// <summary>
		/// 
		/// </summary>
		public static void PopThreadCallContextId()
		{
		}
*/
		/// <summary>
		/// 
		/// </summary>
		/// <param name="metricId"></param>
		/// <param name="value"></param>
		private static void SendMetric( Guid metricId, double value )
		{
			// Check params
			DebugCode.AssertNotEmpty( metricId );
			// value can be any double

			if( CheckReadyToSend() )
			{
				Hashtable table = new Hashtable();
				table[ TableNames.SoftwareId ] = s_softwareId;
				table[ TableNames.MetricId ] = metricId;
				table[ TableNames.DateUTC ] = DateTime.UtcNow;
				table[ TableNames.MachineName ] = Environment.MachineName;
				table[ TableNames.Value ] = value;

				SendMessage( table, MessageLabelNames.AppMetric );
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="e"></param>
		/// <param name="message"></param>
		/// <param name="args"></param>
		private static void SendException( Exception e, string message, params object[] args )
		{
			// Check params
			DebugCode.AssertNotNull( e );
			// message can be null or empty
			// args can be null

			if( CheckReadyToSend() )
			{
				Hashtable table = new Hashtable();
				table[ TableNames.SoftwareId ] = s_softwareId;
				table[ TableNames.DateUTC ] = DateTime.UtcNow;
				table[ TableNames.MachineName ] = Environment.MachineName;
				table[ TableNames.Message ] = string.Format( message, args );
				table[ TableNames.SourceMethod ] = TraceDumper.GetCallingMethod( new StackFrame( 2, true ) );
				table[ TableNames.ExceptionType ] = e.GetType().FullName;
				table[ TableNames.ExceptionString ] = TraceDumper.BuildDumpException( 3, e, message, args );

				SendMessage( table, MessageLabelNames.AppException );
			}
		}
/*
		/// <summary>
		/// 
		/// </summary>
		/// <param name="methodName"></param>
		/// <param name="callContext"></param>
		/// <param name="seconds"></param>
		internal static void SendMethodTimer( string methodName, Guid callContextId, double seconds )
		{
			// Check params
			DebugCode.AssertNotEmpty( methodName );
			// callContext can be empty
			DebugCode.AssertRange( seconds, 0.0, double.MaxValue );

			if( CheckReadyToSend() )
			{
				Hashtable table = new Hashtable();
				table[ TableNames.SoftwareId ] = s_softwareId;
				table[ TableNames.DateUTC ] = DateTime.UtcNow;
				table[ TableNames.MachineName ] = Environment.MachineName;
				table[ TableNames.SourceMethod ] = methodName;
				table[ TableNames.CallContextId ] = callContextId;
				table[ TableNames.Value ] = seconds;

				SendMessage( table, MessageLabelNames.AppMethodTime );
			}
		}
*/
		/// <summary>
		/// 
		/// </summary>
		/// <param name="table"></param>
		/// <param name="messageLabel"></param>
		private static void SendMessage( Hashtable table, string messageLabel )
		{
			try
			{
				s_queue.Send( table, messageLabel );
			}
			catch( Exception e )
			{
				TraceCode.ExceptionIf( traceSwitch.TraceError, e, "Exception sending message" );
			}
		}

		/// <summary>
		/// Setup the message queue
		/// </summary>
		private static void OpenMessageQueue()
		{
			// Turn things off by default
			s_sendMessages = false;

			string env = ConfigurationSettings.AppSettings[ "DeploymentEnvironment" ];
			if( env != null )
			{
				if( env == "Programmer" )
				{
					s_sendMessages = true;
				}
			}
			
			if( !s_sendMessages )
			{
				return;
			}

			try
			{
				// Get the IP Address and setup the format name
				IPHostEntry ipHost = Dns.GetHostByName( "queue.monitor.app" );
				IPAddress ipAddress = ipHost.AddressList[ 0 ];
				string formatName = string.Format( @"FORMATNAME:DIRECT=TCP:{0}\private$\monitoring", ipAddress.ToString() );

				TraceCode.WriteLineIf( traceSwitch.TraceInfo, "Opening queue {0}", formatName );

				s_queue = new MessageQueue( formatName );
				s_queue.Formatter = new BinaryMessageFormatter();

				s_queue.DefaultPropertiesToSend.Priority = MessagePriority.Normal;
				s_queue.DefaultPropertiesToSend.TimeToReachQueue = TimeSpan.FromMinutes( 5.0 );
				s_queue.DefaultPropertiesToSend.TimeToBeReceived = TimeSpan.FromHours( 24.0 );
			}
			catch( Exception e )
			{
				s_queue = null;
				TraceCode.ExceptionIf( traceSwitch.TraceError, e, "Exception opening message queue" );
			}
		}

		/// <summary>
		/// 
		/// </summary>
		private static bool CheckReadyToSend()
		{
			if( s_softwareId == Guid.Empty )
			{
				throw new ArgumentException( "Application Id has not been set." );
			}

			if( !s_sendMessages )
			{
				return false;
			}

			if( s_queue == null )
			{
				OpenMessageQueue();
			}

			if( s_queue == null )
			{
				return false;
			}

			return true;
		}

		/// <summary>
		/// This class is used by the message receivers to sync the name element of
		/// the tables/messages that are being sent over the message queue.
		/// </summary>
		public class TableNames
		{
			/// <summary></summary>
			public const string AgentDeviceId = "AgentDeviceId";

			/// <summary></summary>
			public const string CallContextId = "CallContextId";

			/// <summary></summary>
			public const string DateUTC = "DateUTC";

			/// <summary></summary>
			public const string DeviceId = "DeviceId";

			/// <summary></summary>
			public const string ExceptionString = "ExceptionString";

			/// <summary></summary>
			public const string ExceptionType = "ExceptionType";

			/// <summary></summary>
			public const string HttpStatusCode = "HttpStatusCode";

			/// <summary></summary>
			public const string MachineName = "MachineName";

			/// <summary></summary>
			public const string Message = "Message";

			/// <summary></summary>
			public const string MetricId = "MetricId";

			/// <summary></summary>
			public const string SoftwareId = "SoftwareId";

			/// <summary></summary>
			public const string SourceMethod = "SourceMethod";

			/// <summary></summary>
			public const string Value = "Value";
		}

		/// <summary>
		/// This class is used by the message receivers to sync the label 
		/// property for messages that are being sent over the message queue.
		/// </summary>
		public class MessageLabelNames
		{
			/// <summary></summary>
			public const string AppException = "AppException";

			// /// <summary></summary>
			//public const string AppMethodTime = "AppMethodTime";

			/// <summary></summary>
			public const string AppMetric = "AppMetric";

			/// <summary></summary>
			public const string MetricWebPages = "MetricWebPages";
			
			// /// <summary></summary>
			//public const string Xml = "XML";
		}
	}
}
